//
// (C) Copyright 2009 Irantha Suwandarathna (irantha@gmail.com)
// All rights reserved.
//

/* Copyright (c) 1995-2000, The Hypersonic SQL Group.
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the Hypersonic SQL Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE HYPERSONIC SQL GROUP,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * on behalf of the Hypersonic SQL Group.
 *
 *
 * For work added by the HSQL Development Group:
 *
 * Copyright (c) 2001-2008, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use _in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions _in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer _in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */


using System;
using System.Collections.Generic;
using System.Text;
using System.Linq;
using System.Reflection;
using EffiProzDB.Lib;

namespace EffiProzDB
{

    // fredt@users 20020320 - doc 1.7.0 - update
    // fredt@users 20020315 - patch 1.7.0 - switch for scripting
    // fredt@users 20020130 - patch 476694 by velichko - transaction savepoints
    // additions _in different parts to support savepoint transactions
    // fredt@users 20020910 - patch 1.7.1 by fredt - database _readonly enforcement
    // fredt@users 20020912 - patch 1.7.1 by fredt - permanent internal connection
    // boucherb@users 20030512 - patch 1.7.2 - compiled statements
    //                                       - session becomes execution hub
    // boucherb@users 20050510 - patch 1.7.2 - generalized Result packet passing
    //                                         based command execution
    //                                       - batch execution handling
    // fredt@users 20030628 - patch 1.7.2 - session proxy support
    // fredt@users 20040509 - patch 1.7.2 - SQL conformance for CURRENT_TIMESTAMP and other datetime functions

    /**
     *  Implementation of a user session with the database. In 1.7.2 Session
     *  becomes the public interface to an HSQLDB database, accessed locally or
     *  remotely via SessionInterface.
     *
     *  When as Session is closed, all references to internal engine objects are
     *  set to null. But the session id and scripting mode may still be used for
     *  scripting
     *
     * New class based based on original Hypersonic code.
     * Extensively rewritten and extended _in successive versions of HSQLDB.
     *
     * @author Thomas Mueller (Hypersonic SQL Group)
     * @version 1.8.0
     * @since 1.7.0
     */
    public class Session : SessionInterface
    {

        //    //
        private volatile bool _isAutoCommit;
        private volatile bool _isReadOnly;
        private volatile bool _isClosed;

        //    //
        public Database database;
        private User user;
        public List<Transaction> rowActionList;
        private bool _isNestedTransaction;
        private int nestedOldTransIndex;
        public int isolationMode = (int)SessionTxConsts.TX_READ_COMMITTED;
        public long actionTimestamp;
        public long transactionTimestamp;
        private int currentMaxRows;
        private int sessionMaxRows;
        private int lastIdentity = 0;
        private int sessionId;
        public HashMappedList<string, int> savepoints;
        private bool script;
        private Tokenizer tokenizer;
        private Parser parser;
        public static  Result emptyUpdateCount =
           new Result(ResultConstants.UPDATECOUNT);

        //
        // private jdbcConnection intConnection;

        // schema
        public HsqlNameManager.HsqlName currentSchema;
        public HsqlNameManager.HsqlName loggedSchema;
        private HsqlNameManager.HsqlName oldSchema;

        // query processing
        public bool _isProcessingScript;
        public bool _isProcessingLog;

        // two types of temp tables
        private Dictionary<int, Node> indexArrayMap;
        private Dictionary<int, Node> indexArrayKeepMap;

        /** @todo fredt - clarify _in which circumstances Session has to disconnect */
        public Session getSession()
        {
            return this;
        }

        /**
         * Constructs a new Session object.
         *
         * @param  db the database to which this represents a connection
         * @param  user the initial user
         * @param  autocommit the initial autocommit value
         * @param  _readonly the initial _readonly value
         * @param  id the session identifier, as known to the database
         */
        public Session(Database db, User user, bool autocommit, bool _readonly,
                int id)
        {

            sessionId = id;
            database = db;
            this.user = user;
            rowActionList = new List<Transaction>();
            savepoints = new HashMappedList<string, int>(4);
            _isAutoCommit = autocommit;
            _isReadOnly = _readonly;
            dbCommandInterpreter = new DatabaseCommandInterpreter(this);
            compiledStatementExecutor = new CompiledStatementExecutor(this);
            compiledStatementManager = db.compiledStatementManager;
            tokenizer = new Tokenizer();
            parser = new Parser(this, database, tokenizer);

            resetSchema();
        }

        public void resetSchema()
        {

            HsqlNameManager.HsqlName initialSchema = user.getInitialSchema();

            currentSchema = ((initialSchema == null)
                             ? database.schemaManager.getDefaultSchemaHsqlName()
                             : initialSchema);
        }

        /**
         *  Retrieves the session identifier for this Session.
         *
         * @return the session identifier for this Session
         */
        public int getId()
        {
            return sessionId;
        }

        /**
         * Closes this Session.
         */
        public void close()
        {

            if (_isClosed)
            {
                return;
            }

            lock (database)
            {

                // test again inside block
                if (_isClosed)
                {
                    return;
                }

                database.sessionManager.removeSession(this);
                rollback();

                try
                {
                    database.logger.writeToLog(this, Token.T_DISCONNECT);
                }
                catch (HsqlException ) { }

                clearIndexRoots();
                clearIndexRootsKeep();
                compiledStatementManager.removeSession(sessionId);
                database.closeIfLast();

                database = null;
                user = null;
                rowActionList = null;
                savepoints = null;
                // intConnection             = null;
                compiledStatementExecutor = null;
                compiledStatementManager = null;
                dbCommandInterpreter = null;
                lastIdentity = int.MinValue;
                _isClosed = true;
            }
        }

        /**
         * Retrieves whether this Session is closed.
         *
         * @return true if this Session is closed
         */
        public bool isClosed()
        {
            return _isClosed;
        }

        public void setIsolation(int level)
        {
            isolationMode = level;
        }

        public int getIsolation()
        {
            return isolationMode;
        }

        /**
         * Setter for iLastIdentity attribute.
         *
         * @param  i the new value
         */
        public void setLastIdentity(int i)
        {
            lastIdentity = i;
        }

        /**
         * Getter for iLastIdentity attribute.
         *
         * @return the current value
         */
        public int getLastIdentity()
        {
            return lastIdentity;
        }

        /**
         * Retrieves the Database instance to which this
         * Session represents a connection.
         *
         * @return the Database object to which this Session is connected
         */
        public Database getDatabase()
        {
            return database;
        }

        /**
         * Retrieves the name, as known to the database, of the
         * user currently controlling this Session.
         *
         * @return the name of the user currently connected within this Session
         */
        public string getUsername()
        {
            return user.getName();
        }

        /**
         * Retrieves the User object representing the user currently controlling
         * this Session.
         *
         * @return this Session's User object
         */
        public User getUser()
        {
            return user;
        }

        /**
         * Sets this Session's User object to the one specified by the
         * user argument.
         *
         * @param  user the new User object for this session
         */
        public void setUser(User user)
        {
            this.user = user;
        }

        public int getMaxRows()
        {
            return currentMaxRows;
        }

        public int getSQLMaxRows()
        {
            return sessionMaxRows;
        }

        /**
         * The SQL command SET MAXROWS n will override the Statement.setMaxRows(n)
         * until SET MAXROWS 0 is issued.
         *
         * NB this is dedicated to the SET MAXROWS sql statement and should not
         * otherwise be called. (fredt@users)
         */
        public void setSQLMaxRows(int rows)
        {
            currentMaxRows = sessionMaxRows = rows;
        }

        /**
         * Checks whether this Session's current User has the privileges of
         * the ADMIN role.
         *
         * @  if this Session's User does not have the
         *      privileges of the ADMIN role.
         */
        public void checkAdmin()
        {
            user.checkAdmin();
        }

        /**
         * Checks whether this Session's current User has the set of rights
         * specified by the right argument, _in relation to the database
         * object identifier specified by the object argument.
         *
         * @param  object the database object to check
         * @param  right the rights to check for
         * @throws  HsqlException if the Session User does not have such rights
         */
        public void check(HsqlNameManager.HsqlName _object, int right)
        {
            user.check(_object, right);
        }

        /**
         * Checks the rights on a function
         *
         * @param  object the function
         * @throws  HsqlException if the Session User does not have such rights
         */
        public void check(string _object)
        {
            user.check(_object);
        }

        /**
         * This is used for reading - writing to existing tables.
         * @throws  HsqlException
         */
        public void checkReadWrite()
        {

            if (_isReadOnly)
            {
                throw Trace.error(Trace.DATABASE_IS_READONLY);
            }
        }

        /**
         * This is used for creating new database objects such as tables.
         * @throws  HsqlException
         */
        public void checkDDLWrite()
        {

            if (database.isFilesReadOnly() && !user.isSys())
            {
                throw Trace.error(Trace.DATABASE_IS_READONLY);
            }

            checkReadWrite();
        }

        /**
         *  Adds a single-row deletion step to the transaction UNDO buffer.
         *
         * @param  table the table from which the row was deleted
         * @param  row the deleted row
         * @throws  HsqlException
         */
        public bool addDeleteAction(Table table, Row row)
        {

            if (!_isAutoCommit || _isNestedTransaction)
            {
                Transaction t = new Transaction(true, table, row, actionTimestamp);

                rowActionList.Add(t);
                database.txManager.addTransaction(this, t);

                return true;
            }
            else
            {
                table.removeRowFromStore(row);
            }

            return false;
        }

        /**
         *  Adds a single-row insertion step to the transaction UNDO buffer.
         *
         * @param  table the table into which the row was inserted
         * @param  row the inserted row
         * @throws  HsqlException
         */
        public bool addInsertAction(Table table, Row row)
        {

            if (!_isAutoCommit || _isNestedTransaction)
            {
                Transaction t = new Transaction(false, table, row,
                                                actionTimestamp);

                rowActionList.Add(t);
                database.txManager.addTransaction(this, t);

                return true;
            }
            else
            {
                table.commitRowToStore(row);
            }

            return false;
        }

        /**
         *  Setter for the autocommit attribute.
         *
         * @param  autocommit the new value
         * @throws  HsqlException
         */
        public void setAutoCommit(bool autocommit)
        {

            if (_isClosed)
            {
                return;
            }

            lock (database)
            {
                if (autocommit != _isAutoCommit)
                {
                    commit();

                    _isAutoCommit = autocommit;

                    try
                    {
                        database.logger.writeToLog(this, getAutoCommitStatement());
                    }
                    catch (HsqlException) { }
                }
            }
        }

        public void startPhasedTransaction() { }

        public void prepareCommit() { }

        /**
         * Commits any uncommited transaction this Session may have open
         *
         * @throws  HsqlException
         */
        public void commit()
        {

            if (_isClosed)
            {
                return;
            }

            lock (database)
            {
                if (rowActionList.Count != 0)
                {
                    try
                    {
                        database.logger.writeCommitStatement(this);
                    }
                    catch (HsqlException ) { }
                }

                database.txManager.commit(this);
                clearIndexRoots();
            }
        }

        /**
         * Rolls back any uncommited transaction this Session may have open.
         *
         * @throws  HsqlException
         */
        public void rollback()
        {

            if (_isClosed)
            {
                return;
            }

            lock (database)
            {
                if (rowActionList.Count != 0)
                {
                    try
                    {
                        database.logger.writeToLog(this, Token.T_ROLLBACK);
                    }
                    catch (HsqlException) { }
                }

                database.txManager.rollback(this);
                clearIndexRoots();
            }
        }

        /**
         * No-op _in this implementation
         */
        public void resetSession()
        {
            throw new HsqlException("", "", 0);
        }

        /**
         *  Implements a transaction SAVEPOINT. A new SAVEPOINT with the
         *  name of an existing one replaces the old SAVEPOINT.
         *
         * @param  name of the savepoint
         * @throws  HsqlException if there is no current transaction
         */
        public void savepoint(string name)
        {

            savepoints.Remove(name);
            savepoints.Add(name, (rowActionList.Count));

            try
            {
                database.logger.writeToLog(this, Token.T_SAVEPOINT + " " + name);
            }
            catch (HsqlException) { }
        }

        /**
         *  Implements a partial transaction ROLLBACK.
         *
         * @param  name Name of savepoint that was marked before by savepoint()
         *      call
         * @throws  HsqlException
         */
        public void rollbackToSavepoint(string name)
        {

            if (_isClosed)
            {
                return;
            }

            try
            {
                database.logger.writeToLog(this,
                                           Token.T_ROLLBACK + " " + Token.T_TO
                                           + " " + Token.T_SAVEPOINT + " " + name);
            }
            catch (HsqlException ) { }

            database.txManager.rollbackSavepoint(this, name);
        }

        /**
         * Implements release of named SAVEPOINT.
         *
         * @param  name Name of savepoint that was marked before by savepoint()
         *      call
         * @throws  HsqlException if name does not correspond to a savepoint
         */
        public void releaseSavepoint(string name)
        {

            // remove this and all later savepoints
            int index = savepoints.Keys.IndexOf(name);

            Trace.check(index >= 0, Trace.SAVEPOINT_NOT_FOUND, name);

            while (savepoints.Count > index)
            {
                savepoints.Remove(savepoints.Keys[savepoints.Count - 1]);
            }
        }

        /**
         * Starts a nested transaction.
         *
         * @throws  HsqlException
         */
        public void beginNestedTransaction()
        {

            if (_isNestedTransaction)
            {
                Trace.doAssert(false, "beginNestedTransaction");
            }

            nestedOldTransIndex = rowActionList.Count;
            _isNestedTransaction = true;

            if (_isAutoCommit)
            {
                try
                {
                    database.logger.writeToLog(this, "SET AUTOCOMMIT FALSE");
                }
                catch (HsqlException) { }
            }
        }

        /**
         * @todo -- fredt 20050604 - if this method is called after an _out of memory
         * error during update, the next block might throw _out of memory too and as
         * a result inNestedTransaction remains true and no further update
         * is possible. The session must be closed at that point by the user
         * application.
         */

        /**
         * Ends a nested transaction.
         *
         * @param  rollback true to roll back or false to commit the nested transaction
         * @throws  HsqlException
         */
        public void endNestedTransaction(bool rollback)
        {

            if (!_isNestedTransaction)
            {
                Trace.doAssert(false, "endNestedTransaction");
            }

            if (rollback)
            {
                database.txManager.rollbackTransactions(this, nestedOldTransIndex,
                        true);
            }

            // reset after the rollback
            _isNestedTransaction = false;

            if (_isAutoCommit)
            {
                database.txManager.commit(this);

                try
                {
                    database.logger.writeToLog(this, "SET AUTOCOMMIT TRUE");
                }
                catch (HsqlException) { }
            }
        }

        /**
         * Setter for _readonly attribute.
         *
         * @param  _readonly the new value
         */
        public void setReadOnly(bool _readonly)
        {

            if (!_readonly && database.databaseReadOnly)
            {
                throw Trace.error(Trace.DATABASE_IS_READONLY);
            }

            _isReadOnly = _readonly;
        }

        /**
         *  Getter for _readonly attribute.
         *
         * @return the current value
         */
        public bool isReadOnly()
        {
            return _isReadOnly;
        }

        /**
         *  Getter for nestedTransaction attribute.
         *
         * @return the current value
         */
        bool isNestedTransaction()
        {
            return _isNestedTransaction;
        }

        /**
         *  Getter for autoCommit attribute.
         *
         * @return the current value
         */
        public bool isAutoCommit()
        {
            return _isAutoCommit;
        }

        /**
         *  A switch to set scripting on the basis of type of statement executed.
         *  A method _in DatabaseCommandInterpreter.java sets this value to false
         *  before other  methods are called to act on an SQL statement, which may
         *  set this to true. Afterwards the method reponsible for logging uses
         *  getScripting() to determine if logging is required for the executed
         *  statement. (fredt@users)
         *
         * @param  script The new scripting value
         */
        public void setScripting(bool script)
        {
            this.script = script;
        }

        /**
         * Getter for scripting attribute.
         *
         * @return  scripting for the last statement.
         */
        public bool getScripting()
        {
            return script;
        }

        public string getAutoCommitStatement()
        {
            return _isAutoCommit ? "SET AUTOCOMMIT TRUE"
                                : "SET AUTOCOMMIT FALSE";
        }

        /**
         * Retrieves an internal Connection object equivalent to the one
         * that created this Session.
         *
         * @return  internal connection.
         */
        //public jdbcConnection getInternalConnection()
        //{

        //    if (intConnection == null)
        //    {
        //        intConnection = new jdbcConnection(this);
        //    }

        //    return intConnection;
        //}

        // boucherb@users 20020810 metadata 1.7.2
        //----------------------------------------------------------------
        private static readonly long connectTime = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;

        // more effecient for MetaData concerns than checkAdmin

        /**
         * Getter for admin attribute.
         *
         * @ return the current value
         */
        public bool isAdmin()
        {
            return user.isAdmin();
        }

        /**
         * Getter for connectTime attribute.
         *
         * @return the value
         */
        public long getConnectTime()
        {
            return connectTime;
        }

        /**
         * Getter for transactionSise attribute.
         *
         * @return the current value
         */
        public int getTransactionSize()
        {
            return rowActionList.Count;
        }

        /**
         * Retrieves whether the database object identifier by the dbobject
         * argument is accessible by the current Session User.
         *
         * @return true if so, else false
         */
        public bool isAccessible(string dbobject)
        {
            return user.isAccessible(dbobject);
        }

        public bool isAccessible(HsqlNameManager.HsqlName dbobject)
        {
            return user.isAccessible(dbobject);
        }

        // boucherb@users 20030417 - patch 1.7.2 - compiled statement support
        //-------------------------------------------------------------------
        public DatabaseCommandInterpreter dbCommandInterpreter;
        public CompiledStatementExecutor compiledStatementExecutor;
        public CompiledStatementManager compiledStatementManager;

        public CompiledStatement sqlCompileStatement(string sql)
        {

            parser.reset(sql);

            CompiledStatement cs = null;
            int brackets = 0;
            string token = tokenizer.getString();
            int cmd = Token.get(token);

            switch (cmd)
            {

                case Token.OPENBRACKET:
                    {
                        brackets = parser.parseOpenBracketsSelect() + 1;
                        break;
                    }
                case Token.SELECT:
                    {
                        cs = parser.compileSelectStatement(brackets);

                        break;
                    }
                case Token.INSERT:
                    {
                        cs = parser.compileInsertStatement();

                        break;
                    }
                case Token.UPDATE:
                    {
                        cs = parser.compileUpdateStatement();

                        break;
                    }
                case Token.DELETE:
                    {
                        cs = parser.compileDeleteStatement();

                        break;
                    }
                case Token.CALL:
                    {
                        cs = parser.compileCallStatement();

                        break;
                    }
                default:
                    {

                        // DDL statements
                        cs = new CompiledStatement(currentSchema);

                        break;
                    }
            }

            // In addition to requiring that the compilation was successful,
            // we also require that the submitted sql represents a _single_
            // valid DML or DDL statement. We do not check the DDL yet.
            // fredt - now accepts semicolon and whitespace at the end of statement
            // fredt - investigate if it should or not for prepared statements
            if (cs.type != CompiledStatement.DDL)
            {
                while (tokenizer.getPosition() < tokenizer.getLength())
                {
                    token = tokenizer.getString();

                    if (token.Length != 0 && !token.Equals(Token.T_SEMICOLON))
                    {
                        throw Trace.error(Trace.UNEXPECTED_TOKEN, token);
                    }
                }
            }

            // - need to be able to key cs against its sql _in statement pool
            // - also need to be able to revalidate its sql occasionally
            cs.sql = sql;

            return cs;
        }

        /**
         * Executes the command encapsulated by the cmd argument.
         *
         * @param cmd the command to execute
         * @return the result of executing the command
         */
        public Result execute(Result cmd)
        {

            try
            {
                if (_isClosed)
                {
                    Trace.check(false, Trace.ACCESS_IS_DENIED,
                                Trace.getMessage(Trace.Session_execute));
                }
            }
            catch (Exception t)
            {
                return new Result(t, null);
            }

            lock (database)
            {
                int type = cmd.mode;

                if (sessionMaxRows == 0)
                {
                    currentMaxRows = cmd.updateCount;
                }

                // we simply get the next system change number - no matter what type of query
                actionTimestamp = database.txManager.nextActionTimestamp();

               // System.GC.Collect();

                switch (type)
                {

                    case ResultConstants.SQLEXECUTE:
                        {
                            Result resultout = sqlExecute(cmd);

                            resultout = performPostExecute(resultout);

                            return resultout;
                        }
                    case ResultConstants.BATCHEXECUTE:
                        {
                            Result resultout = sqlExecuteBatch(cmd);

                            resultout = performPostExecute(resultout);

                            return resultout;
                        }
                    case ResultConstants.SQLEXECDIRECT:
                        {
                            Result resultout =
                                sqlExecuteDirectNoPreChecks(cmd.getMainString());

                            resultout = performPostExecute(resultout);

                            return resultout;
                        }
                    case ResultConstants.BATCHEXECDIRECT:
                        {
                            Result resultout = sqlExecuteBatchDirect(cmd);

                            resultout = performPostExecute(resultout);

                            return resultout;
                        }
                    case ResultConstants.SQLPREPARE:
                        {
                            CompiledStatement cs;

                            try
                            {
                                cs = compiledStatementManager.compile(
                                    this, cmd.getMainString());
                            }
                            catch (Exception t)
                            {
                                return new Result(t, cmd.getMainString());
                            }

                            Result rmd = cs.describeResult();
                            Result pmd = cs.describeParameters();

                            return Result.newPrepareResponse(cs.id, rmd, pmd);
                        }
                    case ResultConstants.SQLFREESTMT:
                        {
                            compiledStatementManager.freeStatement(
                                cmd.getStatementID(), sessionId, false);

                            return emptyUpdateCount;
                        }
                    case ResultConstants.GETSESSIONATTR:
                        {
                            return getAttributes();
                        }
                    case ResultConstants.SETSESSIONATTR:
                        {
                            return setAttributes(cmd);
                        }
                    case ResultConstants.SQLENDTRAN:
                        {
                            switch (cmd.getEndTranType())
                            {

                                case ResultConstants.COMMIT:
                                    commit();
                                    break;

                                case ResultConstants.ROLLBACK:
                                    rollback();
                                    break;

                                case ResultConstants.SAVEPOINT_NAME_RELEASE:
                                    try
                                    {
                                        string name = cmd.getMainString();

                                        releaseSavepoint(name);
                                    }
                                    catch (Exception t)
                                    {
                                        return new Result(t, null);
                                    }
                                    break;

                                case ResultConstants.SAVEPOINT_NAME_ROLLBACK:
                                    try
                                    {
                                        rollbackToSavepoint(cmd.getMainString());
                                    }
                                    catch (Exception t)
                                    {
                                        return new Result(t, null);
                                    }
                                    break;

                                // not yet
                                // case ResultConstants.COMMIT_AND_CHAIN :
                                // case ResultConstants.ROLLBACK_AND_CHAIN :
                            }

                            return emptyUpdateCount;
                        }
                    case ResultConstants.SQLSETCONNECTATTR:
                        {
                            switch (cmd.getConnectionAttrType())
                            {

                                case ResultConstants.SQL_ATTR_SAVEPOINT_NAME:
                                    try
                                    {
                                        savepoint(cmd.getMainString());
                                    }
                                    catch (Exception t)
                                    {
                                        return new Result(t, null);
                                    }
                                    break;

                                // case ResultConstants.SQL_ATTR_AUTO_IPD
                                //   - always true
                                // default: throw - case never happens
                            }

                            return emptyUpdateCount;
                        }
                    case ResultConstants.SQLDISCONNECT:
                        {
                            close();

                            return emptyUpdateCount;
                        }
                    default:
                        {
                            return new Result(
                                Trace.runtimeError(
                                    Trace.UNSUPPORTED_INTERNAL_OPERATION,
                                    "Session.execute()"), null);
                        }
                }
            }
        }

        private Result performPostExecute(Result r)
        {

            try
            {
                if (database != null)
                {
                    database.schemaManager.logSequences(this, database.logger);

                    if (_isAutoCommit)
                    {
                        clearIndexRoots();
                        database.logger.synchLog();
                    }
                }

                return r;
            }
            catch (Exception e)
            {
                return new Result(e, null);
            }
            finally
            {
                if (database != null && database.logger.needsCheckpoint())
                {
                    try
                    {
                        database.logger.checkpoint(false);
                    }
                    catch (HsqlException)
                    {
                        database.logger.appLog.logContext(
                            SimpleLog.LOG_ERROR, "checkpoint did not complete");
                    }
                }
            }
        }

        public Result sqlExecuteDirectNoPreChecks(string sql)
        {
            lock (database)
            {
                return dbCommandInterpreter.execute(sql);
            }
        }

        public Result sqlExecuteCompiledNoPreChecks(CompiledStatement cs,
                                             Object[] pvals)
        {
            return compiledStatementExecutor.execute(cs, pvals);
        }

        private Result sqlExecuteBatch(Result cmd)
        {

            int csid;
            Record record;
            Result _out;
            CompiledStatement cs;
            Expression[] parameters;
            int[] updateCounts;
            int count;

            csid = cmd.getStatementID();
            cs = database.compiledStatementManager.getStatement(this, csid);

            if (cs == null)
            {

                // invalid sql has been removed already
                return new Result(
                    Trace.runtimeError(Trace.INVALID_PREPARED_STATEMENT, null),
                    null);
            }

            parameters = cs.parameters;
            count = 0;
            updateCounts = new int[cmd.getSize()];
            record = cmd.rRoot;

            while (record != null)
            {
                Result _in;
                Object[] pvals = record.data;

                _in = sqlExecuteCompiledNoPreChecks(cs, pvals);

                // On the client side, iterate over the vals and throw
                // a BatchUpdateException if a batch status value of
                // esultConstants.EXECUTE_FAILED is encountered _in the result
                if (_in.mode == ResultConstants.UPDATECOUNT)
                {
                    updateCounts[count++] = _in.updateCount;
                }
                else if (_in.isData())
                {

                    // FIXME:  we don't have what it takes yet
                    // to differentiate between things like
                    // stored procedure calls to methods with
                    // void return type and select statements with
                    // a single row/column containg null
                    updateCounts[count++] = ResultConstants.SUCCESS_NO_INFO;
                }
                else
                {
                    updateCounts = ArrayUtil.arraySlice(updateCounts, 0, count);

                    break;
                }

                record = record.next;
            }

            _out = new Result(ResultConstants.SQLEXECUTE, updateCounts, 0);

            return _out;
        }

        private Result sqlExecuteBatchDirect(Result cmd)
        {

            Record record;
            Result _out;
            int[] updateCounts;
            int count;

            count = 0;
            updateCounts = new int[cmd.getSize()];
            record = cmd.rRoot;

            while (record != null)
            {
                Result _in;
                string sql = (String)record.data[0];

                try
                {
                    _in = dbCommandInterpreter.execute(sql);
                }
                catch (Exception)
                {
                    _in = new Result(ResultConstants.ERROR);

                    // if (t is OutOfMemoryError) {
                    // System.gc();
                    // }
                    // "in" alread equals "err"
                    // maybe test for OOME and do a gc() ?
                    // t.printStackTrace();
                }

                // On the client side, iterate over the colType vals and throw
                // a BatchUpdateException if a batch status value of
                // ResultConstants.EXECUTE_FAILED is encountered
                if (_in.mode == ResultConstants.UPDATECOUNT)
                {
                    updateCounts[count++] = _in.updateCount;
                }
                else if (_in.isData())
                {

                    // FIXME:  we don't have what it takes yet
                    // to differentiate between things like
                    // stored procedure calls to methods with
                    // void return type and select statements with
                    // a single row/column containg null
                    updateCounts[count++] = ResultConstants.SUCCESS_NO_INFO;
                }
                else
                {
                    updateCounts = ArrayUtil.arraySlice(updateCounts, 0, count);

                    break;
                }

                record = record.next;
            }

            _out = new Result(ResultConstants.SQLEXECUTE, updateCounts, 0);

            return _out;
        }

        /**
         * Retrieves the result of executing the prepared statement whose csid
         * and parameter values/types are encapsulated by the cmd argument.
         *
         * @return the result of executing the statement
         */
        private Result sqlExecute(Result cmd)
        {

            int csid = cmd.getStatementID();
            CompiledStatement cs = compiledStatementManager.getStatement(this,
                csid);

            if (cs == null)
            {

                // invalid sql has been removed already
                return new Result(
                    Trace.runtimeError(Trace.INVALID_PREPARED_STATEMENT, null),
                    null);
            }

            Object[] pvals = cmd.getParameterData();

            return sqlExecute(cs, pvals);
        }

        private Result sqlExecute(CompiledStatement cs, Object[] pvals)
        {
            return sqlExecuteCompiledNoPreChecks(cs, pvals);
        }

        // session DATETIME functions
        long currentDateTimeSCN;
        long currentMillis;
        DateTime? currentDate;
        DateTime? currentTime;
        DateTime? currentTimestamp;

        /**
         * Returns the current date, unchanged for the duration of the current
         * execution unit (statement).<p>
         *
         * SQL standards require that CURRENT_DATE, CURRENT_TIME and
         * CURRENT_TIMESTAMP are all evaluated at the same point of
         * time _in the duration of each SQL statement, no matter how long the
         * SQL statement takes to complete.<p>
         *
         * When this method or a corresponding method for CURRENT_TIME or
         * CURRENT_TIMESTAMP is first called _in the scope of a system change
         * number, currentMillis is set to the current system time. All further
         * CURRENT_XXXX calls _in this scope will use this millisecond value.
         * (fredt@users)
         */
        public DateTime? getCurrentDate()
        {

            if (currentDateTimeSCN != actionTimestamp)
            {
                currentDateTimeSCN = actionTimestamp;
                currentMillis = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
                currentDate = DateTime.Now;
                currentTime = null;
                currentTimestamp = null;
            }
            else if (currentDate == null)
            {
                currentDate = DateTime.Now;
            }

            return currentDate;
        }

        /**
         * Returns the current time, unchanged for the duration of the current
         * execution unit (statement)
         */
        public DateTime? getCurrentTime()
        {

            if (currentDateTimeSCN != actionTimestamp)
            {
                currentDateTimeSCN = actionTimestamp;
                currentMillis = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
                currentDate = null;
                currentTime =
                    DateTime.Now;
                currentTimestamp = null;
            }
            else if (currentTime == null)
            {
                currentTime =
                    DateTime.Now;
            }

            return currentTime;
        }

        /**
         * Returns the current timestamp, unchanged for the duration of the current
         * execution unit (statement)
         */
        public DateTime? getCurrentTimestamp()
        {

            if (currentDateTimeSCN != actionTimestamp)
            {
                currentDateTimeSCN = actionTimestamp;
                currentMillis = DateTime.Now.Ticks / TimeSpan.TicksPerMillisecond;
                currentDate = null;
                currentTime = null;
                currentTimestamp = DateTime.Now;
            }
            else if (currentTimestamp == null)
            {
                currentTimestamp = DateTime.Now;
            }

            return currentTimestamp;
        }

        public Result getAttributes()
        {

            Result r = Result.newSessionAttributesResult();
            Object[] row = new Object[] {
                    database.getURI(), getUsername(), (sessionId),
                    (isolationMode),
                    (_isAutoCommit),
                    (database.databaseReadOnly),
                    (_isReadOnly)
                };

            r.add(row);

            return r;
        }

        public Result setAttributes(Result r)
        {

            Object[] row = r.rRoot.data;

            for (int i = 0; i < row.Length; i++)
            {
                Object value = row[i];

                if (value == null)
                {
                    continue;
                }

                try
                {
                    switch (i)
                    {

                        case (int)SessionInfoConsts.INFO_AUTOCOMMIT:
                            {
                                this.setAutoCommit(((bool)value));

                                break;
                            }
                        case (int)SessionInfoConsts.INFO_CONNECTION_READONLY:
                            this.setReadOnly(((bool)value));
                            break;
                    }
                }
                catch (HsqlException e)
                {
                    return new Result(e, null);
                }
            }

            return emptyUpdateCount;
        }

        // DatabaseMetaData.getURL should work as specified for
        // internal connections too.
        public string getInternalConnectionURL()
        {
            return DatabaseURL.S_URL_PREFIX + database.getURI();
        }

        public bool isProcessingScript()
        {
            return _isProcessingScript;
        }

        public bool isProcessingLog()
        {
            return _isProcessingLog;
        }

        public bool isSchemaDefintion()
        {
            return oldSchema != null;
        }

        public void startSchemaDefinition(string schema)
        {

            if (_isProcessingScript)
            {
                setSchema(schema);

                return;
            }

            oldSchema = currentSchema;

            setSchema(schema);
        }

        public void endSchemaDefinition()
        {

            if (oldSchema == null)
            {
                return;
            }

            currentSchema = oldSchema;
            oldSchema = null;

            database.logger.writeToLog(this,
                                       "SET SCHEMA "
                                       + currentSchema.statementName);
        }

        // schema object methods
        public void setSchema(string schema)
        {
            currentSchema = database.schemaManager.getSchemaHsqlName(schema);
        }

        /**
         * If schemaName is null, return the current schema name, else return
         * the HsqlNameManager.HsqlName object for the schema. If schemaName does not exist,
         * throw.
         */
        public HsqlNameManager.HsqlName getSchemaHsqlName(string name)
        {
            return name == null ? currentSchema
                                : database.schemaManager.getSchemaHsqlName(name);
        }

        /**
         * Same as above, but return string
         */
        public string getSchemaName(string name)
        {
            return name == null ? currentSchema.name
                                : database.schemaManager.getSchemaName(name);
        }

        /**
         * If schemaName is null, return the current schema name, else return
         * the HsqlNameManager.HsqlName object for the schema. If schemaName does not exist, or
         * schema _readonly, throw.
         */
        public HsqlNameManager.HsqlName getSchemaHsqlNameForWrite(string name)
        {

            HsqlNameManager.HsqlName schema = getSchemaHsqlName(name);

            if (database.schemaManager.isSystemSchema(schema))
            {
                throw Trace.error(Trace.INVALID_SCHEMA_NAME_NO_SUBCLASS);
            }

            return schema;
        }

        /**
         * Same as above, but return string
         */
        public string getSchemaNameForWrite(string name)
        {

            HsqlNameManager.HsqlName schema = getSchemaHsqlNameForWrite(name);

            return schema.name;
        }

        /**
         * get the root for a temp table index
         */
        public Node getIndexRoot(HsqlNameManager.HsqlName index, bool preserve)
        {
            Node node = null;
            if (preserve)
            {
                if (indexArrayKeepMap == null)
                {
                    return null;
                }

                if(indexArrayKeepMap.TryGetValue(index.GetHashCode(), out node))
                    return node;               
            }
            else
            {
                if (indexArrayMap == null)
                {
                    return null;
                }

                if (indexArrayMap.TryGetValue(index.GetHashCode(), out node))
                    return node;                 
            }

            return null;

        }

        /**
         * set the root for a temp table index
         */
        public void setIndexRoot(HsqlNameManager.HsqlName index, bool preserve, Node root)
        {

            if (preserve)
            {
                if (indexArrayKeepMap == null)
                {
                    if (root == null)
                    {
                        return;
                    }

                    indexArrayKeepMap = new Dictionary<int, Node>();
                }

                indexArrayKeepMap[index.GetHashCode()] = root;
            }
            else
            {
                if (indexArrayMap == null)
                {
                    if (root == null)
                    {
                        return;
                    }

                    indexArrayMap = new Dictionary<int, Node>();
                }

                indexArrayMap[index.GetHashCode()] = root;
            }
        }

        public void dropIndex(HsqlNameManager.HsqlName index, bool preserve)
        {

            if (preserve)
            {
                if (indexArrayKeepMap != null)
                {
                    indexArrayKeepMap.Remove(index.GetHashCode());
                }
            }
            else
            {
                if (indexArrayMap != null)
                {
                    indexArrayMap.Remove(index.GetHashCode());
                }
            }
        }

        /**
         * clear default temp table contents for this session
         */
        public void clearIndexRoots()
        {

            if (indexArrayMap != null)
            {
                indexArrayMap.Clear();
            }
        }

        /**
         * clear ON COMMIT PRESERVE temp table contents for this session
         */
        public void clearIndexRootsKeep()
        {

            if (indexArrayKeepMap != null)
            {
                indexArrayKeepMap.Clear();
            }
        }

        // warnings
        List<HsqlException> sqlWarnings;

        public void addWarning(HsqlException warning)
        {

            if (sqlWarnings == null)
            {
                sqlWarnings = new List<HsqlException>();
            }

            sqlWarnings.Add(warning);
        }

        public HsqlException[] getAndClearWarnings()
        {

            if (sqlWarnings == null)
            {
                return new HsqlException[0];
            }

            HsqlException[] array = sqlWarnings.ToArray();

            sqlWarnings.Clear();

            return array;
        }

        /**
    * Retrieves an internal Connection object equivalent to the one
    * that created this Session.
    *
    * @return  internal connection.
    */
        public object getInternalConnection()
        {
            return null;
        }
    }
}